package com.kinyx.framework.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.exception.KRuntimeException;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 注解工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KAnnotationUtils {

	/**
	 * 查找注解
	 * @param annotations 注解数组
	 * @param annotationName 注解名（全名）
	 * @return 注解
	 */
	@ReturnNull("未匹配到该注解")
	public static Annotation find(@CanNotNull final Annotation[] annotations, @CanNotNull final String annotationName) {
		for (final Annotation annotation : annotations) {
			final Class<? extends Annotation> annotationType = annotation.annotationType();
			if (annotationName.equals(annotationType.getName())) { return annotation; }
		}
		return null;
	}

	/** 缓存 */
	private static final Map<String, Class<? extends Annotation>> CACHE = new HashMap<>();
	@SuppressWarnings("unchecked")
	@ReturnNull("注解名反射类型异常")
	private static Class<? extends Annotation> getAnnotationClass(@CanNotNull final String annotationName) {
		Class<? extends Annotation> annotationClass = CACHE.get(annotationName);
		if (annotationClass == null) {
			try {
				annotationClass = (Class<? extends Annotation>) Class.forName(annotationName);
			} catch (final ClassNotFoundException e) {
				annotationClass = null;
			}
			CACHE.put(annotationName, annotationClass);
		}
		return annotationClass;
	}

	/**
	 * 获取注解
	 * @param clazz 类
	 * @param annotationName 注解名（全名）
	 * @return 注解
	 */
	@ReturnNull("注解名反射类型异常")
	@ReturnNull("该类并未标注该注解")
	@Tester("com.kinyx.framework.reflect.KAnnotationUtils_Tester_get")
	public static Annotation get(@CanNotNull final Class<?> clazz, @CanNotNull final String annotationName) {
		final Class<? extends Annotation> annotationClass = getAnnotationClass(annotationName);
		if (annotationClass == null) { return null; }
		return clazz.getAnnotation(annotationClass);
	}

	/**
	 * 获取注解
	 * @param field 字段
	 * @param annotationName 注解名（全名）
	 * @return 注解
	 */
	@ReturnNull("注解名反射类型异常")
	@ReturnNull("该字段并未标注该注解")
	@Tester("com.kinyx.framework.reflect.KAnnotationUtils_Tester_get")
	public static Annotation get(@CanNotNull final Field field, @CanNotNull final String annotationName) {
		final Class<? extends Annotation> annotationClass = getAnnotationClass(annotationName);
		if (annotationClass == null) { return null; }
		return field.getAnnotation(annotationClass);
	}

	/**
	 * 获取注解值
	 * @param annotation 注解
	 * @param valueFieldName 注解值字段名
	 * @return 注解值
	 */
	@ReturnNotNull
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "NoSuchMethodException", when = "Class.getDeclaredMethod(String)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "SecurityException", when = "Class.getDeclaredMethod(String)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "IllegalAccessException", when = "Method.invoke(Object...)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "IllegalArgumentException", when = "Method.invoke(Object...)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "InvocationTargetException", when = "Method.invoke(Object...)")
	@Tester("com.kinyx.framework.reflect.KAnnotationUtils_Tester_getValue")
	public static Object getValue(@CanNotNull final Annotation annotation, @CanNotNull final String valueFieldName) {
		final Class<? extends Annotation> clazz = annotation.getClass();
		Method method;
		try {
			method = clazz.getDeclaredMethod(valueFieldName);
		} catch (final NoSuchMethodException e) {
			throw new KRuntimeException(e);
		} catch (final SecurityException e) {
			throw new KRuntimeException(e);
		}
		try {
			return method.invoke(annotation);
		} catch (final IllegalAccessException e) {
			throw new KRuntimeException(e);
		} catch (final IllegalArgumentException e) {
			throw new KRuntimeException(e);
		} catch (final InvocationTargetException e) {
			throw new KRuntimeException(e);
		}
	}

	/**
	 * 获取注解值
	 * @param clazz 类
	 * @param annotationName 注解名（全名）
	 * @param valueFieldName 注解值字段名
	 * @return 注解值
	 */
	@ReturnNull("注解名反射类型异常")
	@ReturnNull("该类并未标注该注解")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "NoSuchMethodException", when = "Class.getDeclaredMethod(String)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "SecurityException", when = "Class.getDeclaredMethod(String)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "IllegalAccessException", when = "Method.invoke(Object...)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "IllegalArgumentException", when = "Method.invoke(Object...)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "InvocationTargetException", when = "Method.invoke(Object...)")
	@Tester("com.kinyx.framework.reflect.KAnnotationUtils_Tester_getValue")
	public static Object getValue(@CanNotNull final Class<?> clazz, @CanNotNull final String annotationName, @CanNotNull final String valueFieldName) {
		final Annotation annotation = get(clazz, annotationName);
		return annotation == null ? null : getValue(annotation, valueFieldName);
	}

	/**
	 * 获取注解值
	 * @param field 字段
	 * @param annotationName 注解名（全名）
	 * @param valueFieldName 注解值字段名
	 * @return 注解值
	 */
	@ReturnNull("注解名反射类型异常")
	@ReturnNull("该字段并未标注该注解")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "NoSuchMethodException", when = "Class.getDeclaredMethod(String)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "SecurityException", when = "Class.getDeclaredMethod(String)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "IllegalAccessException", when = "Method.invoke(Object...)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "IllegalArgumentException", when = "Method.invoke(Object...)")
	@Throws(at = "KAnnotationUtils.getValue(Annotation, String)", value = "InvocationTargetException", when = "Method.invoke(Object...)")
	@Tester("com.kinyx.framework.reflect.KAnnotationUtils_Tester_getValue")
	public static Object getValue(@CanNotNull final Field field, @CanNotNull final String annotationName, @CanNotNull final String valueFieldName) {
		final Annotation annotation = get(field, annotationName);
		return annotation == null ? null : getValue(annotation, valueFieldName);
	}

}
